﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Xml;
using QuantBox.XApi;
using Skyline;

namespace QuantBox
{
    internal static class HdfXmlReader
    {
        private static readonly string H5DumpPath;
        private const int PriceMulti = 10000;

        static HdfXmlReader()
        {
            var path = Path.GetDirectoryName(typeof(HdfXmlReader).Assembly.Location);
            H5DumpPath = Path.Combine(path ?? throw new InvalidOperationException(), "bin64\\h5dump.exe");
        }

        private static (IDictionary<string, int>, string) GetDataSet(string file)
        {
            const string dataPath =
                "/hdf5:HDF5-File/hdf5:RootGroup/hdf5:Group/hdf5:Dataset/hdf5:Data/hdf5:DataFromFile";
            const string fieldPath =
                "/hdf5:HDF5-File/hdf5:RootGroup/hdf5:Group/hdf5:Dataset/hdf5:DataType/hdf5:CompoundType/hdf5:Field";

            var result = (new Dictionary<string, int>(), string.Empty);

            var (output, _) = new ExternalCall(H5DumpPath).Arguments($"--xml \"{file}\"").WinExecWithPipe();
            var doc = new XmlDocument();
            var nsmgr = new XmlNamespaceManager(doc.NameTable);
            doc.LoadXml(output);
            if (doc.DocumentElement != null) {
                nsmgr.AddNamespace("hdf5", doc.DocumentElement.NamespaceURI);
            }
            var node = doc.SelectSingleNode(dataPath, nsmgr);
            if (node != null) {
                result.Item2 = node.InnerText;
            }
            var fieldsNode = doc.SelectNodes(fieldPath, nsmgr);
            if (fieldsNode != null) {
                for (var i = 0; i < fieldsNode.Count; i++) {
                    var attributes = fieldsNode[i].Attributes;
                    if (attributes != null)
                        result.Item1.Add(attributes[0].Value, i);
                }
            }
            return result;
        }

        private static DateTime ParseDate(string date)
        {
            return DateTime.ParseExact(date, "yyyyMMdd", null);
        }

        private static TimeSpan ParseTime(string time)
        {
            var first = time[0];
            switch (first) {
                case '0':
                case '1':
                case '2':
                    break;
                default:
                    time = "0" + time;
                    break;
            }
            return TimeSpan.ParseExact(time, time.Length == 9 ? "hhmmssfff" : "hhmmssff", null);
        }

        private static DepthMarketDataField? ReadStock(IDictionary<string, int> fields, IReadOnlyList<string> items)
        {
            var tick = new DepthMarketDataField();
            tick.Asks = new DepthField[1];
            tick.Bids = new DepthField[1];

            if (fields.TryGetValue("TradeDate", out var index)) {
                tick.SetExchangeDate(ParseDate(items[index]));
            }
            if (fields.TryGetValue("ExTime", out index)) {
                if (items[index] == "0") {
                    return null;
                }
                tick.SetExchangeTime(ParseTime(items[index]));
            }
            if (fields.TryGetValue("instrument", out index)) {
                tick.Symbol = items[index].Replace("\"", "");
            }
            if (fields.TryGetValue("LastPx", out index)) {
                tick.LastPrice = double.Parse(items[index]) / PriceMulti;
            }
            if (fields.TryGetValue("Volume", out index)) {
                tick.Volume = long.Parse(items[index]);
            }
            if (fields.TryGetValue("Open_Int", out index)) {
                tick.OpenInterest = (int)double.Parse(items[index]);
            }
            if (fields.TryGetValue("OfferPx[0]", out index)) {
                tick.Asks[0].Price = double.Parse(items[index]) / PriceMulti;
            }
            if (fields.TryGetValue("OfferVol[0]", out index)) {
                tick.Asks[0].Size = int.Parse(items[index]);
            }
            if (fields.TryGetValue("BidPx[0]", out index)) {
                tick.Bids[0].Price = double.Parse(items[index]) / PriceMulti;
            }
            if (fields.TryGetValue("BidVol[0]", out index)) {
                tick.Bids[0].Size = int.Parse(items[index]);
            }
            if (fields.TryGetValue("Turnover", out index)) {
                tick.Turnover = double.Parse(items[index]);
            }
            return tick;
        }

        private static DepthMarketDataField ReadIndex(IDictionary<string, int> fields, IReadOnlyList<string> items)
        {
            var tick = new DepthMarketDataField();
            tick.Asks = new DepthField[1];
            tick.Bids = new DepthField[1];
            if (fields.TryGetValue("TradeDate", out var index)) {
                tick.SetExchangeDate(ParseDate(items[index]));
            }
            if (fields.TryGetValue("TradeTime", out index)) {
                tick.SetExchangeTime(ParseTime(items[index]));
            }
            if (fields.TryGetValue("instrument", out index)) {
                tick.Symbol = items[index].Replace("\"", "");
            }
            if (fields.TryGetValue("LastIndex", out index)) {
                tick.LastPrice = double.Parse(items[index]) / PriceMulti;
            }
            if (fields.TryGetValue("TotalVolumeTraded", out index)) {
                tick.Volume = long.Parse(items[index]);
            }
            if (fields.TryGetValue("Turnover", out index)) {
                tick.Turnover = double.Parse(items[index]);
            }
            return tick;
        }
        private static DepthMarketDataField ReadFutureOption(IDictionary<string, int> fields, string[] items)
        {
            var tick = new DepthMarketDataField();
            tick.Asks = new DepthField[1];
            tick.Bids = new DepthField[1];

            if (fields.TryGetValue("index", out var index)) {
                tick.SetExchangeDateTime(GetDateTime(long.Parse(items[index])));
            }
            if (fields.TryGetValue("Instrument", out index)) {
                tick.Symbol = items[index].Replace("\"", "");
            }
            if (fields.TryGetValue("Price", out index)) {
                tick.LastPrice = double.Parse(items[index]);
            }
            if (fields.TryGetValue("Volume", out index)) {
                tick.Volume = long.Parse(items[index]);
            }
            if (fields.TryGetValue("Open_Int", out index)) {
                tick.OpenInterest = (int)double.Parse(items[index]);
            }
            if (fields.TryGetValue("SP1", out index)) {
                tick.Asks[0].Price = double.Parse(items[index]);
            }
            if (fields.TryGetValue("SV1", out index)) {
                tick.Asks[0].Size = int.Parse(items[index]);
            }
            if (fields.TryGetValue("BP1", out index)) {
                tick.Bids[0].Price = double.Parse(items[index]);
            }
            if (fields.TryGetValue("BV1", out index)) {
                tick.Bids[0].Size = int.Parse(items[index]);
            }
            if (fields.TryGetValue("Turnover", out index)) {
                tick.Turnover = double.Parse(items[index]);
            }
            return tick;

            DateTime GetDateTime(long v)
            {
                return Utility.Epoch.AddSeconds((double)v / 1000000000);
            }
        }
        public static DepthMarketDataField[] Read(string file, string type)
        {
            var (fields, data) = GetDataSet(file);
            var lines = data.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
            var ticks = new List<DepthMarketDataField>(lines.Length);
            var last = DepthMarketDataField.Empty;
            last.Volume = 0;
            last.Turnover = 0;
            foreach (var line in lines) {
                var items = line.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
                if (items.Length < fields.Count) {
                    continue;
                }
                DepthMarketDataField? tick;
                if (type == DataCenterConst.Index) {
                    tick = ReadIndex(fields, items);
                }
                else if (type == DataCenterConst.FutureOption) {
                    tick = ReadFutureOption(fields, items);
                }
                else {
                    tick = ReadStock(fields, items);
                }

                if (tick == null) {
                    continue;
                }

                if (type != DataCenterConst.FutureOption) {
                    var temp = tick.Copy();
                    tick.Volume -= last.Volume;
                    tick.Turnover -= last.Turnover;
                    last = temp;
                }
                ticks.Add(tick);
            }
            return ticks.ToArray();
        }

        public static DepthMarketDataField[] Read(Stream stream, string type)
        {
            var data = new byte[stream.Length];
            stream.Read(data, 0, (int)stream.Length);
            var h5File = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N"));
            try {
                File.WriteAllBytes(h5File, data);
                return Read(h5File, type);
            }
            finally {
                if (File.Exists(h5File)) {
                    File.Delete(h5File);
                }
            }
        }
    }
}